use std::{env, fs};

use pest::Parser;
use pest_derive::Parser;

#[derive(Parser)]
#[grammar = "lexer.pest"] // 指向你的 .pest 文件
struct SysYLexer;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Token<'a> {
    Integer(i64),
    Identifier(&'a str),
    Const(&'a str),
    Int(&'a str),
    Void(&'a str),
    If(&'a str),
    Else(&'a str),
    While(&'a str),
    Break(&'a str),
    Continue(&'a str),
    Return(&'a str),
    Plus(&'a str),
    Minus(&'a str),
    Mul(&'a str),
    Div(&'a str),
    Mod(&'a str),
    Assign(&'a str),
    Eq(&'a str),
    Neq(&'a str),
    Lt(&'a str),
    Gt(&'a str),
    Le(&'a str),
    Ge(&'a str),
    Not(&'a str),
    And(&'a str),
    Or(&'a str),
    LeftParen(&'a str),
    RightParen(&'a str),
    LeftBrace(&'a str),
    RightBrace(&'a str),
    LeftBracket(&'a str),
    RightBracket(&'a str),
    Comma(&'a str),
    Semicolon(&'a str),
}

fn main() {
    // 收集命令行参数
    let args: Vec<String> = env::args().collect();

    // 检查是否提供了文件名
    if args.len() < 2 {
        eprintln!("Usage: {} <filename>", args[0]);
        std::process::exit(1);
    }

    // 获取文件名
    let filename = &args[1];

    // 读取输入文件
    let input = fs::read_to_string(filename).expect("Failed to read file");

    // 词法分析
    let tokens = tokenize(&input);

    // handle error
    if tokens.is_empty() {
        parse_error(&input);
    }
}

fn parse_error(input: &str) {
    match SysYLexer::parse(Rule::FILE_ERR, input) {
        Ok(mut pairs) => {
            let lex_parser = pairs.next().unwrap();
            for line in lex_parser.into_inner() {
                let (line_num, _) = line.line_col();
                match line.as_rule() {
                    Rule::ERROR => {
                        eprintln!(
                            "Error type A at Line {}: Mysterious character \"{}\".",
                            line_num,
                            line.as_str()
                        )
                    },
                    _ => ()
                }
            }
        },
        Err(_) => ()
    }
}

fn tokenize<'a>(input: &'a str) -> Vec<Token<'a>> {
    let mut tokens = vec![];
    match SysYLexer::parse(Rule::FILE, input) {
        Ok(mut pairs) => {
            let lex_parser = pairs.next().unwrap();
            for line in lex_parser.into_inner() {
                if Rule::VALIDE_LEX != line.as_rule() {
                    continue;
                }
                let (line_num, _) = line.line_col();
                let lex_pair = line.into_inner().next().unwrap();
                let lex_token = match lex_pair.as_rule() {
                    Rule::CONST => Token::Const(lex_pair.as_str()),
                    Rule::INT => Token::Int(lex_pair.as_str()),
                    Rule::VOID => Token::Void(lex_pair.as_str()),
                    Rule::IF => Token::If(lex_pair.as_str()),
                    Rule::ELSE => Token::Else(lex_pair.as_str()),
                    Rule::WHILE => Token::While(lex_pair.as_str()),
                    Rule::BREAK => Token::Break(lex_pair.as_str()),
                    Rule::CONTINUE => Token::Continue(lex_pair.as_str()),
                    Rule::RETURN => Token::Return(lex_pair.as_str()),
                    Rule::PLUS => Token::Plus(lex_pair.as_str()),
                    Rule::MINUS => Token::Minus(lex_pair.as_str()),
                    Rule::MUL => Token::Mul(lex_pair.as_str()),
                    Rule::DIV => Token::Div(lex_pair.as_str()),
                    Rule::MOD => Token::Mod(lex_pair.as_str()),
                    Rule::ASSIGN => Token::Assign(lex_pair.as_str()),
                    Rule::EQ => Token::Eq(lex_pair.as_str()),
                    Rule::NEQ => Token::Neq(lex_pair.as_str()),
                    Rule::LT => Token::Lt(lex_pair.as_str()),
                    Rule::GT => Token::Gt(lex_pair.as_str()),
                    Rule::LE => Token::Le(lex_pair.as_str()),
                    Rule::GE => Token::Ge(lex_pair.as_str()),
                    Rule::NOT => Token::Not(lex_pair.as_str()),
                    Rule::AND => Token::And(lex_pair.as_str()),
                    Rule::OR => Token::Or(lex_pair.as_str()),
                    Rule::L_PAREN => Token::LeftParen(lex_pair.as_str()),
                    Rule::R_PAREN => Token::RightParen(lex_pair.as_str()),
                    Rule::L_BRACE => Token::LeftBrace(lex_pair.as_str()),
                    Rule::R_BRACE => Token::RightBrace(lex_pair.as_str()),
                    Rule::L_BRACKET => Token::LeftBracket(lex_pair.as_str()),
                    Rule::R_BRACKET => Token::RightBracket(lex_pair.as_str()),
                    Rule::COMMA => Token::Comma(lex_pair.as_str()),
                    Rule::SEMICOLON => Token::Semicolon(lex_pair.as_str()),
                    Rule::IDENT => Token::Identifier(lex_pair.as_str()),
                    Rule::INTEGER_CONST => {
                        let integer_pair = lex_pair.into_inner().next().unwrap();
                        let int_num = match integer_pair.as_rule() {
                            Rule::INT_DEC => i64::from_str_radix(integer_pair.as_str(), 10).unwrap(),
                            Rule::INT_OCT => i64::from_str_radix(
                                integer_pair.as_str().trim_start_matches("0"),
                                8
                            ).unwrap(),
                            Rule::INT_HEX => i64::from_str_radix(
                                integer_pair.as_str()
                                    .trim_start_matches("0x")
                                    .trim_start_matches("0X"),
                                16
                            ).unwrap(),
                            _ => unreachable!(),
                        };
                        Token::Integer(int_num)
                    },
                    _ => unreachable!(),
                };
                print_lex_info(lex_token.clone(), line_num);
                tokens.push(lex_token);
            }
            tokens
        },
        Err(_) => {
            tokens
        }
    }
}

fn print_lex_info(lex: Token, line_num: usize) {
    let token_str = match lex {
        Token::Integer(int_num) => format!("INTEGER_CONST {}", int_num.to_string()),
        Token::Identifier(iden) => format!("IDENT {}", iden),
        Token::Const(const_str) => format!("CONST {}", const_str),
        Token::Int(int_str) => format!("INT {}", int_str),
        Token::Void(void_str) => format!("VOID {}", void_str),
        Token::If(if_str) => format!("IF {}", if_str),
        Token::Else(else_str) => format!("ELSE {}", else_str),
        Token::While(while_str) => format!("WHILE {}", while_str),
        Token::Break(break_str) => format!("BREAK {}", break_str),
        Token::Continue(continue_str) => format!("CONTINUE {}", continue_str),
        Token::Return(return_str) => format!("RETURN {}", return_str),
        Token::Plus(plus_str) => format!("PLUS {}", plus_str),
        Token::Minus(minus_str) => format!("MINUS {}", minus_str),
        Token::Mul(mul_str) => format!("MUL {}", mul_str),
        Token::Div(div_str) => format!("DIV {}", div_str),
        Token::Mod(mod_str) => format!("MOD {}", mod_str),
        Token::Assign(assign_str) => format!("ASSIGN {}", assign_str),
        Token::Eq(eq_str) => format!("EQ {}", eq_str),
        Token::Neq(neq_str) => format!("NEQ {}", neq_str),
        Token::Lt(lt) => format!("LT {}", lt),
        Token::Gt(gt) => format!("GT {}", gt),
        Token::Le(le) => format!("LE {}", le),
        Token::Ge(ge) => format!("GE {}", ge),
        Token::Not(not) => format!("NOT {}", not),
        Token::And(and) => format!("AND {}", and),
        Token::Or(or) => format!("OR {}", or),
        Token::LeftParen(l_paren) => format!("L_PAREN {}", l_paren),
        Token::RightParen(r_paren) => format!("R_PAREN {}", r_paren),
        Token::LeftBrace(l_brace) => format!("L_BRACE {}", l_brace),
        Token::RightBrace(r_brace) => format!("R_BRACE {}", r_brace),
        Token::LeftBracket(l_brack) => format!("L_BRACKT {}", l_brack),
        Token::RightBracket(r_brack) => format!("R_BRACKT {}", r_brack),
        Token::Comma(comma) => format!("COMMA {}", comma),
        Token::Semicolon(semicol) => format!("SEMICOLON {}", semicol),
    };
    eprintln!("{} at Line {}.", token_str, line_num);
}
